/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package coalescent.cef.phylo;

import coalescent.data.K69Data;
import coalescent.phylogeny.GeneTree;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.TreeLayout;
import edu.uci.ics.jung.graph.DelegateTree;
import edu.uci.ics.jung.graph.Forest;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.decorators.ConstantDirectionalEdgeValueTransformer;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.layout.PersistentLayout;
import edu.uci.ics.jung.visualization.util.VertexShapeFactory;
import org.apache.commons.collections15.Transformer;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.Objects;

import static coalescent.phylogeny.GeneTree.Edge;
import static coalescent.phylogeny.GeneTree.Node;

/**
 * @author Susanta Tewari
 * @version version
 * @history Created on 10/16/13.
 * @since since
 */
public class GeneTreeDrawer_Base {

    protected int offset = 15;
    protected final GeneTree tree;
    protected final Forest<Node, Edge> forest;
    protected Layout<Node, Edge> layout;
    private VisualizationViewer<Node, Edge> view;
    private String layoutPath;

    public GeneTreeDrawer_Base(K69Data data) {

        Objects.requireNonNull(data);

        this.tree = GeneTree.getInstance(data);
        forest    = new DelegateTree<>();

        forest.addVertex(tree.getRoot());
        buildForest(forest, tree.getRoot());

        layout = new TreeLayout(forest, 100, 100);
    }

    /**
     * @param data infinite-sites data
     * @param layoutPath file to store tree layout;
     * @throws IllegalArgumentException if {@code layoutPath} represents invalid file path
     */
    public GeneTreeDrawer_Base(K69Data data, String layoutPath) {

        this(data);


        // validate layoutPath
        final File file = new File(layoutPath);

        if (!file.exists()) {

            try {
                file.createNewFile();    // checks file path & crates it; both must be atomic- see javadoc
            } catch (IOException e) {
                throw new IllegalArgumentException(e);
            }
        }

        this.layoutPath = layoutPath;
        this.layout     = new JUNG_PLayoutImpl_NodeLabel(tree, layout);
    }

    public boolean isLayoutSavable() {
        return layoutPath != null;
    }

    /**
     * @throws IllegalStateException if {@code isLayoutSavable() == false}
     * @throws RuntimeException if any error occurs during saving
     */
    public void saveLayout() {

        try {
            ((PersistentLayout) layout).persist(layoutPath);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void buildForest(final Forest<Node, Edge> tree, final Node node) {

        final java.util.List<Node> children = node.getChildren();

        for (final Node child : children) {

            tree.addEdge(node.getChildEdge(child), node, child);

            if (!child.isLeaf()) {
                buildForest(tree, child);
            }
        }
    }

    public VisualizationViewer<Node, Edge> getViewer() {

        if (view == null) view = createViewer(forest, layout);

        return view;
    }

    private VisualizationViewer<Node, Edge> createViewer(final Forest<Node, Edge> tree,
            final Layout<Node, Edge> layout) {

        final VisualizationViewer<Node, Edge> vv = new VisualizationViewer<>(layout);


        // background
        vv.setBackground(Color.white);


        // adjust vertex label
        vv.getRenderContext().setVertexLabelTransformer(new Transformer<Node, String>() {

            @Override
            public String transform(final Node node) {
                return getNodeLabel(node);
            }

        });


        // adjust vertex color
        vv.getRenderContext().setVertexFillPaintTransformer(new Transformer<Node, Paint>() {

            @Override
            public Paint transform(final Node node) {
                return getNodeColor(node);
            }

        });


        // adjust vertex size
        final VertexShapeFactory vertexShapeFactory = new VertexShapeFactory(new Transformer<Node,
                                                          Integer>() {

            @Override
            public Integer transform(final Node node) {
                return getVertexSize(node, vv);
            }

        }, new Transformer<Node, Float>() {

            @Override
            public Float transform(final Node node) {
                return 1.0f;
            }

        });

        vv.getRenderContext().setVertexShapeTransformer(new Transformer<Node, Shape>() {

            @Override
            public Shape transform(final Node node) {
                return getVertexShape(node, vertexShapeFactory);
            }

        });


        // vertex label position
        vv.getRenderer().getVertexLabelRenderer().setPosition(
            edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position.CNTR);


        // configure the edge
        vv.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller());
        vv.getRenderContext().setEdgeShapeTransformer(new EdgeShape.Line());
        vv.getRenderContext().setEdgeLabelClosenessTransformer(
            new ConstantDirectionalEdgeValueTransformer(0.5, 0.5));
        modifyViewer(vv);

        return vv;
    }

    /**
     * @param node
     * @return label to be drawn; if null draws nothing
     */
    protected String getNodeLabel(Node node) {

        String val = node.getLabel();

        if (node.isLeaf()) val += ":" + tree.getFreq(node);

        return val;
    }

    protected Color getNodeColor(Node node) {
        return Color.WHITE;
    }

    protected Shape getVertexShape(Node node, VertexShapeFactory vertexShapeFactory) {
        return vertexShapeFactory.getEllipse(node);
    }

    protected Integer getVertexSize(Node node, VisualizationViewer<Node, Edge> vv) {

        final String val = vv.getRenderContext().getVertexLabelTransformer().transform(node);
        final int result = vv.getFontMetrics(vv.getFont()).stringWidth(val) + offset;

        return result;    // return Math.min(result, 75);
    }

    protected void modifyViewer(VisualizationViewer<Node, Edge> vv) {}
}
